#include <stdlib.h>
#include <string.h>

#include <common/macro/build.h>
#include <common/macro/array.h>
#include <common/macro/debug.h>

#include <storage/tuple/tuple.h>
#include <storage/tuple/page_tuple.h>

API_LIBLOCAL page_tuple_t *page_tuple_new(page_t *common) {
	page_tuple_t *page_tuple;

	page_tuple = malloc(sizeof(*page_tuple));
	page_tuple->common = common;
	page_tuple->tuples = NULL;
	page_tuple->tuples_c = 0;

	/*
	 * The number of items eats up space, even though none
	 * are presented.
	 */
	page_decrease_freespace(page_tuple->common, sizeof(page_tuple->tuples_c));

	return page_tuple;
}

API_LIBLOCAL int page_tuple_insert(page_tuple_t *page_tuple, tuple_t *tuple) {
	/* Our assumption is that we already know that the tuple fits in the page. */
	/*
	 * Assign data space. The last tuple should be the end
	 * border of the free data space. If it wasn't, we'd have
	 * to look it up.
	 */
	if (page_tuple->tuples_c > 0) {
		tuple->offset = page_tuple->tuples[page_tuple->tuples_c - 1].offset +
					  tuple_datasize(&page_tuple->tuples[page_tuple->tuples_c - 1]);
	} else {
		tuple->offset = 0;
	}
	assert(tuple->offset < page_tuple->common->size);
	assert(page_tuple->tuples_c < 254);

	/*
	 * Serialization necessities have been taken care of, so
	 * we're pretty much done.
	 */
	ARRAY_APPEND(page_tuple->tuples, page_tuple->tuples_c, *tuple);

	/* Update free space status. */
	page_decrease_freespace(page_tuple->common, tuple_headersize(tuple));
	page_decrease_freespace(page_tuple->common, tuple_datasize(tuple));

	return 0;
}

API_LIBLOCAL int page_tuple_delete(page_tuple_t *page_tuple, page_item_t id) {
	page_item_t i;
	tuple_t *tuple;

	debugmsg("Delete: %u/%u\n", id, page_tuple->tuples_c);
	if (id >= page_tuple->tuples_c)
		return -1;

	tuple = &page_tuple->tuples[id];
	page_increase_freespace(page_tuple->common, tuple_headersize(tuple));
	page_increase_freespace(page_tuple->common, tuple_datasize(tuple));

	for (i = id + 1; i < page_tuple->tuples_c; i++) {
		page_tuple->tuples[i].offset -= tuple->offset;
	}
	tuple_deinit(tuple);

	if (page_tuple->tuples_c - 1 > id) {
		debugmsg("Move.\n");
		memmove(&page_tuple->tuples[id], &page_tuple->tuples[id+1], (page_tuple->tuples_c - id - 1) * (sizeof(*page_tuple->tuples)));
	}

	page_tuple->tuples_c--;
	return 0;
}

API_LIBLOCAL int page_tuple_serialize(page_tuple_t *page_tuple, char *out, ssize_t out_size) {
	ssize_t offset, itemlen;
	page_item_t i;

	offset = 0;

	/* Tuple count. */
	itemlen = sizeof(page_tuple->tuples_c);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &page_tuple->tuples_c, itemlen);
	offset += itemlen;

	/* Each individual tuple. */
	for (i = 0; i < page_tuple->tuples_c; i++) {
		debugmsg("Data Offset: %u.\n", offset);
		itemlen = tuple_headersize(&page_tuple->tuples[i]);
		tuple_serialize(&page_tuple->tuples[i], &out[offset], out_size - offset);
		offset += itemlen;
	}

	return 0;
}

API_LIBLOCAL int page_tuple_deserialize(page_tuple_t **page_tuple, char *in, ssize_t in_size, page_t *common) {
	ssize_t offset, itemlen;
	page_item_t i;
	tuple_t tuple, *ptuple;

	offset = 0;

	(*page_tuple) = malloc(sizeof(*(*page_tuple)));
	(*page_tuple)->common = common;
	(*page_tuple)->tuples = NULL;
	(*page_tuple)->tuples_c = 0;

	/* Tuple count. */
	itemlen = sizeof((*page_tuple)->tuples_c);
	if (itemlen > in_size - offset) { return -1; }
	memcpy(&(*page_tuple)->tuples_c, &in[offset], itemlen);
	offset += itemlen;

	/* Allocate. */
	ARRAY_ALLOC((*page_tuple)->tuples, (*page_tuple)->tuples_c);

	/* Each individual tuple. */
	ptuple = &tuple;
	for (i = 0; i < (*page_tuple)->tuples_c; i++) {
		tuple_init(&tuple);
		debugmsg("Data Offset: %u.\n", offset);
		tuple_deserialize(&ptuple, &in[offset], in_size - offset);
		ARRAY_SET((*page_tuple)->tuples, i, tuple);
		offset += tuple_headersize(&tuple);
	}

	return 0;
}

API_LIBLOCAL void page_tuple_destroy(page_tuple_t *page_tuple) {
	page_item_t i;
	if (page_tuple == NULL) return;

	for (i = 0; i < page_tuple->tuples_c; i++)
		tuple_deinit(&page_tuple->tuples[i]);
	free(page_tuple->tuples);
	free(page_tuple);
}

